package ecscore

import (
	"log"
	"reflect"
	"strings"
	"unsafe"

	"github.com/viant/xunsafe"
)

// / Query which returns all entities in the simulation
type AllEntities struct {
	Id EntityId
}

// / Get the index and type of Component
type QueryComponent struct {
	Index uint16
	Type  reflect.Type
}

// / Query abstracts away fetching entities based
// / on their archetype
// / Generic struct
type Query[T any] struct {
	queryComponents []reflect.Type
	components      []reflect.Type
	fields          []*xunsafe.Field
	entityId        *xunsafe.Field
}

// / Read a single entity from the given storage
// / into a pointer towards the inner query type
// / this is useful for reading entities into archetypes
func (q *Query[T]) Read(storage EntityStorage, eid EntityId, targetPtr unsafe.Pointer) {
	if q.entityId != nil {
		q.entityId.SetUint32(targetPtr, eid)
	}

	for index, cpnType := range q.components {
		field := q.fields[index]
		cpn := storage.GetComponent(eid, cpnType)
		field.SetValue(targetPtr, cpn)
	}
}

func (q *Query[T]) Get(sim *Simulation, id EntityId) *T {
	var genericTy T

	res := &genericTy
	// read into res pointer
	q.Read(sim.EStorage, id, unsafe.Pointer(res))
	return res
}

func (q *Query[T]) Execute(sim *Simulation) *QueryResultIterator[T] {
	return q.ExecuteStorage(sim.EStorage)
}

func (q *Query[T]) ExecuteStorage(storage EntityStorage) *QueryResultIterator[T] {
	res := &QueryResultIterator[T]{
		ids:     storage.FindAll(q.queryComponents),
		index:   0,
		storage: storage,
		query:   q,
	}

	res.ptr = unsafe.Pointer(&res.Item)
	return res
}

func NewQuery[T any]() *Query[T] {
	var query T
	queryType := reflect.TypeOf(query)
	if queryType.Kind() != reflect.Struct {
		log.Panicf("Invalid query type: %v, consider provide T as struct", queryType)
		return nil
	}

	result := &Query[T]{
		components:      []reflect.Type{},
		queryComponents: []reflect.Type{},
		fields:          []*xunsafe.Field{},
		entityId:        nil,
	}

	// core bussiness scope
	for fieldIdx := 0; fieldIdx < queryType.NumField(); fieldIdx++ {

		field := queryType.Field(fieldIdx)
		// it the query field is not expose
		if !field.IsExported() {
			continue
		}

		optional := false
		skipped := false

		tags := strings.Split(field.Tag.Get("ecs"), ",")
		for _, tag := range tags {
			if tag == "-" {
				skipped = true
				break
			} else if tag == "optional" {
				optional = true
			}
		}

		if skipped {
			continue
		}

		if field.Type == entityIdType {
			if result.entityId != nil {
				log.Panicf("multiple entity id fields in query: %v", query)
				return nil
			}

			result.entityId = xunsafe.FieldByIndex(queryType, fieldIdx)
			continue
		}

		if !optional {
			result.queryComponents = append(result.queryComponents, field.Type)
		}

		result.components = append(result.components, field.Type)
		result.fields = append(result.fields, xunsafe.FieldByIndex(queryType, fieldIdx))
	}

	// end scope

	return result
}
